<!DOCTYPE html>



  


<html class="theme-next pisces use-motion" lang="zh-Hans">
<head>
  <!-- hexo-inject:begin --><!-- hexo-inject:end --><meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />







<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />

<link href="/css/main.css?v=5.1.3" rel="stylesheet" type="text/css" />


  <link rel="apple-touch-icon" sizes="180x180" href="/images/avatar.jpg?v=5.1.3">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/avatar.jpg?v=5.1.3">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/avatar.jpg?v=5.1.3">


  <link rel="mask-icon" href="/images/avatar.jpg?v=5.1.3" color="#222">





  <meta name="keywords" content="Java,并发," />










<meta name="description" content="Java 内存模型的基础并发编程模型的两个关键问题 线程之间如何通信（线程之间以何种机制来交换信息） 线程之间如何同步（程序中用于控制不同线程间操作发生相对顺序的机制）  在命令式编程中，线程之间通信机制有两种：共享内存和消息传递。  共享内存的并发模型 线程通信：线程之间共享程序的公共状态，通过写-读内存中的公共状态进行隐式通信 线程同步：同步是显式进行的。程序员必须显式指定某个方法或某段代码需">
<meta name="keywords" content="Java,并发">
<meta property="og:type" content="article">
<meta property="og:title" content="（三）Java内存模型">
<meta property="og:url" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/index.html">
<meta property="og:site_name" content="Fighter&#39;s Blog">
<meta property="og:description" content="Java 内存模型的基础并发编程模型的两个关键问题 线程之间如何通信（线程之间以何种机制来交换信息） 线程之间如何同步（程序中用于控制不同线程间操作发生相对顺序的机制）  在命令式编程中，线程之间通信机制有两种：共享内存和消息传递。  共享内存的并发模型 线程通信：线程之间共享程序的公共状态，通过写-读内存中的公共状态进行隐式通信 线程同步：同步是显式进行的。程序员必须显式指定某个方法或某段代码需">
<meta property="og:locale" content="zh-Hans">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/Java内存模型的抽象结构示意图.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/处理器操作内存的执行结果.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/处理器和内存的交互.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/内存屏障类型表.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/3个操作之间的依赖关系.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/程序的两种执行顺序.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/顺序一致性内存模型的视图.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/顺序一致性模型的一种执行效果.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/两个内存模型中的执行时序对比图.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/总线的工作机制.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/happens-before关系.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/共享变量的状态示意图.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/volatile重排序规则表.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/happens-before关系图.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/锁获取的状态示意图.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/concurrent包的实现示意图.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/线程执行时序图.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/线程执行时序图2.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/多线程执行时序图.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/多线程执行时序表.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/两个线程并发执行的示意图.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/处理器内存模型的特征表.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/JMM插入内存屏障的示意图.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/各种CPU内存模型的强弱对比示意图.png">
<meta property="og:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/3类程序的执行结果的对比图.png">
<meta property="og:updated_time" content="2018-03-11T07:50:06.120Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="（三）Java内存模型">
<meta name="twitter:description" content="Java 内存模型的基础并发编程模型的两个关键问题 线程之间如何通信（线程之间以何种机制来交换信息） 线程之间如何同步（程序中用于控制不同线程间操作发生相对顺序的机制）  在命令式编程中，线程之间通信机制有两种：共享内存和消息传递。  共享内存的并发模型 线程通信：线程之间共享程序的公共状态，通过写-读内存中的公共状态进行隐式通信 线程同步：同步是显式进行的。程序员必须显式指定某个方法或某段代码需">
<meta name="twitter:image" content="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/Java内存模型的抽象结构示意图.png">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Pisces',
    version: '5.1.3',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/"/>





  <title>（三）Java内存模型 | Fighter's Blog</title><!-- hexo-inject:begin --><!-- hexo-inject:end -->
  








</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  
  
    
  

  <!-- hexo-inject:begin --><!-- hexo-inject:end --><div class="container sidebar-position-left page-post-detail">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/"  class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">Fighter's Blog</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle">深度沉迷学习</p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br />
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-about">
          <a href="/about/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-user"></i> <br />
            
            关于
          </a>
        </li>
      
        
        <li class="menu-item menu-item-tags">
          <a href="/tags/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br />
            
            标签
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br />
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
            
            归档
          </a>
        </li>
      

      
    </ul>
  

  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="Fighter.">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Fighter's Blog">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">（三）Java内存模型</h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2018-03-04T14:28:35+08:00">
                2018-03-04
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Java笔记/" itemprop="url" rel="index">
                    <span itemprop="name">Java笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          
            <span class="post-meta-divider">|</span>
            <span class="page-pv"><i class="fa fa-file-o"></i>
            <span class="busuanzi-value" id="busuanzi_value_page_pv" ></span>
            </span>
          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        <h2 id="Java-内存模型的基础"><a href="#Java-内存模型的基础" class="headerlink" title="Java 内存模型的基础"></a>Java 内存模型的基础</h2><h3 id="并发编程模型的两个关键问题"><a href="#并发编程模型的两个关键问题" class="headerlink" title="并发编程模型的两个关键问题"></a>并发编程模型的两个关键问题</h3><ol>
<li>线程之间如何<strong>通信</strong>（线程之间以何种机制来<strong>交换信息</strong>）</li>
<li>线程之间如何<strong>同步</strong>（程序中用于<strong>控制不同线程间操作发生相对顺序的机制</strong>）</li>
</ol>
<p>在命令式编程中，线程之间通信机制有两种：共享内存和消息传递。</p>
<ol>
<li>共享内存的并发模型<ul>
<li>线程通信：线程之间共享程序的公共状态，<strong>通过写-读内存中的公共状态进行隐式通信</strong></li>
<li>线程同步：<strong>同步是显式进行的。</strong>程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行。</li>
</ul>
</li>
<li>消息传递的并发模型<ul>
<li>线程通信：线程之间没有公共状态，线程间<strong>必须通过发送消息来显式进行通信</strong></li>
<li>线程同步：由于消息的发送必须在消息的接收之前，因此<strong>同步是隐式进行的。</strong></li>
</ul>
</li>
</ol>
<p><strong>Java 并发采用共享内存模型，Java 线程间通信总是隐式进行，整个通信过程对程序员完全透明。</strong><br><a id="more"></a></p>
<h3 id="Java-内存模型的抽象结构"><a href="#Java-内存模型的抽象结构" class="headerlink" title="Java 内存模型的抽象结构"></a>Java 内存模型的抽象结构</h3><ul>
<li>所有实例域、静态域和数组元素都存储在堆内存中，堆内存在线程之间共享。</li>
<li>局部变量（Local Variables），方法定义参数（Java语言规范称为Formal Method Parameters）和异常处理器参数（ExceptionHandler Parameters）不会在线程之间共享，不会有内存可见性问题，不受内存模型的影响。</li>
<li>Java线程之间的通信由Java内存模型（JMM）控制，JMM决定一个线程对共享变量的写入何时对另一个线程可见。</li>
<li>JMM定义了线程和主内存之间的抽象关系：<br>  线程之间的共享变量存储在主内存中，<strong>每个线程都有一个私有的本地内存（Local Memory），本地内存中存储了该线程以读/写共享变量的副本。</strong> 本地内存是JMM的一个抽象概念，并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。</li>
</ul>
<img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/Java内存模型的抽象结构示意图.png" title="Java内存模型的抽象结构示意图">
<p>线程A与线程B进行通信必须要经历下面2个步骤:</p>
<ol>
<li>线程A把本地内存A中更新过的共享变量刷新到主内存中去。</li>
<li>线程B到主内存中去读取线程A之前已更新过的共享变量。</li>
</ol>
<p>JMM通过控制主内存与每个线程的本地内存之间的交互，来为Java程序员提供内存可见性保证。</p>
<h3 id="从源代码到指令序列的重排序"><a href="#从源代码到指令序列的重排序" class="headerlink" title="从源代码到指令序列的重排序"></a>从源代码到指令序列的重排序</h3><p>为提高性能，编译器和处理器常常会对指令做重排序：</p>
<ol>
<li>编译器重排序<ul>
<li>编译器优化的重排序</li>
</ul>
</li>
<li>处理器重排序<ul>
<li>指令级并行的重排序</li>
<li>内存系统的重排序</li>
</ul>
</li>
</ol>
<p>为解决重排序导致的内存可见性问题，JMM的 编译器/处理器重排序规则 采取如下方法：</p>
<ul>
<li>编译器重排序<ul>
<li>禁止特定类型的编译器重排序（不是所有的编译器重排序都要禁止）</li>
</ul>
</li>
<li>处理器重排序<ul>
<li>Java编译器在生成指令序列时，<strong>插入特定类型的内存屏障（Memory Barriers，Intel称之为Memory Fence）指令，</strong> 通过内存屏障指令来禁止特定类型的处理器重排序。</li>
</ul>
</li>
</ul>
<h3 id="并发编程模型的分类"><a href="#并发编程模型的分类" class="headerlink" title="并发编程模型的分类"></a>并发编程模型的分类</h3><p>现代的处理器使用写缓冲区临时保存向内存写入的数据。写缓冲区好处：</p>
<ul>
<li>保证指令流水线持续运行，避免由于处理器停顿下来等待向内存写入数据而产生延迟。</li>
<li>以批处理的方式刷新写缓冲区，以及合并写缓冲区中对同一内存地址的多次写，减少对内存总线的占用。</li>
</ul>
<p>但是，每个处理器上的写缓冲区，仅对它所在的处理器可见。这会导致<strong>处理器对内存的读/写操作的执行顺序，不一定与内存实际发生的读/写操作顺序一致！</strong><br><img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/处理器操作内存的执行结果.png" title="处理器操作内存的执行结果"><br><img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/处理器和内存的交互.png" title="处理器和内存的交互"><br>由于现代的处理器都会使用写缓冲区，因此现代的处理器都会允许对写-读操作进行重排序。为保证内存可见性，Java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序。</p>
<img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/内存屏障类型表.png" title="内存屏障类型表">
<h3 id="happens-before-规则"><a href="#happens-before-规则" class="headerlink" title="happens-before 规则"></a>happens-before 规则</h3><p>从JDK 5开始，Java使用新的JSR-133内存模型。JSR-133使用happens-before的概念来阐述操作之间的内存可见性。在JMM中，如果一个操作执行的结果需要对另一个操作可见，那么这两个操作（可在一个线程内，也可在不同线程间）之间必须要存在happens-before关系。happens-before 规则如下：</p>
<ul>
<li>程序顺序规则：一个线程中的每个操作，happens-before于该线程中的任意后续操作。</li>
<li>监视器锁规则：对一个锁的解锁，happens-before于随后对这个锁的加锁。</li>
<li>volatile 变量规则：对一个volatile域的写，happens-before于任意后续对这个volatile域的读。</li>
<li>传递性：如果A happens-before B，且B happens-before C，那么A happens-before C。</li>
</ul>
<h2 id="重排序"><a href="#重排序" class="headerlink" title="重排序"></a>重排序</h2><blockquote>
<p>重排序：编译器和处理器为优化程序性能而<strong>对指令序列进行重新排序</strong>的一种手段。</p>
</blockquote>
<h3 id="数据依赖性"><a href="#数据依赖性" class="headerlink" title="数据依赖性"></a>数据依赖性</h3><blockquote>
<p>数据依赖性：如果<strong>两个操作访问同一个变量，且这两个操作中有一个为写操作，</strong> 此时这两个操作之间就存在数据依赖性。（这里所说的数据依赖性针对单处理器和单线程中的操作）</p>
</blockquote>
<ul>
<li>写后读</li>
<li>读后写</li>
<li>写后写</li>
</ul>
<p>上面3种情况，只要重排序两个操作的执行顺序，程序的执行结果就会被改变。<strong>编译器和处理器在重排序时，会遵守数据依赖性，编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。</strong></p>
<h3 id="as-if-serial语义"><a href="#as-if-serial语义" class="headerlink" title="as-if-serial语义"></a>as-if-serial语义</h3><blockquote>
<p>as-if-serial：不管怎么重排序（编译器和处理器为了提高并行度），（单线程）程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">double</span> pi = <span class="number">3.14</span>; <span class="comment">// A</span></div><div class="line"><span class="keyword">double</span> r = <span class="number">1.0</span>; <span class="comment">// B</span></div><div class="line"><span class="keyword">double</span> area = pi * r * r; <span class="comment">// C</span></div></pre></td></tr></table></figure>
<img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/3个操作之间的依赖关系.png" title="3个操作之间的依赖关系">
<p>A和C之间存在数据依赖关系，同时B和C之间也存在数据依赖关系。因此在最终执行的指令序列中，C不能被重排序到A和B的前面。但A和B之间没有数据依赖关系，编译器和处理器可以重排序A和B之间的执行顺序。</p>
<img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/程序的两种执行顺序.png" title="程序的两种执行顺序">
<p>这就给程序员一种单线程程序按顺序执行的“幻觉”。</p>
<h3 id="程序顺序规则"><a href="#程序顺序规则" class="headerlink" title="程序顺序规则"></a>程序顺序规则</h3><p>上述代码存在3个happens-before关系。</p>
<ol>
<li>A　happens-before B。</li>
<li>B　happens-before C。</li>
<li>A　happens-before C。</li>
</ol>
<p>重排序操作A和操作B后的执行结果，与操作A和操作B按happens-before顺序执行的结果一致。在这种情况下，JMM会认为这种重排序并不非法（notillegal），JMM允许这种重排序。</p>
<h3 id="重排序对多线程的影响"><a href="#重排序对多线程的影响" class="headerlink" title="重排序对多线程的影响"></a>重排序对多线程的影响</h3><p>在单线程程序中，对存在控制依赖的操作重排序，不会改变执行结果（这也是as-if-serial语义允许对存在控制依赖的操作做重排序的原因）；但在多线程程序中，对 存在控制依赖或者无数据依赖 的操作重排序，可能会改变程序的执行结果。</p>
<h2 id="顺序一致性"><a href="#顺序一致性" class="headerlink" title="顺序一致性"></a>顺序一致性</h2><h3 id="数据竞争与顺序一致性"><a href="#数据竞争与顺序一致性" class="headerlink" title="数据竞争与顺序一致性"></a>数据竞争与顺序一致性</h3><p>JMM 规范对数据竞争的定义：</p>
<blockquote>
<p>在一个线程中写一个变量，在另一个线程读同一个变量，而且写和读没有通过同步来排序。</p>
</blockquote>
<p>一个多线程程序能正确同步，这个程序将是一个没有数据竞争的程序。如果程序是正确同步的，程序的执行将具有顺序一致性（Sequentially Consistent）——即程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同。这里的同步是指广义上的同步，包括对常用同步原语（synchronized、volatile和final）的正确使用。</p>
<h3 id="顺序一致性内存模型"><a href="#顺序一致性内存模型" class="headerlink" title="顺序一致性内存模型"></a>顺序一致性内存模型</h3><p>两大特性：</p>
<ol>
<li>一个线程中的所有操作必须按照程序的顺序来执行。</li>
<li>（不管程序是否同步）所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中，每个操作都必须原子执行且立刻对所有线程可见。</li>
</ol>
<img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/顺序一致性内存模型的视图.png" title="顺序一致性内存模型的视图">
<p>在概念上，顺序一致性模型有一个单一的全局内存，这个内存通过一个左右摆动的开关可以连接到任意一个线程，同时每一个线程必须按照程序的顺序来执行内存读/写操作。在任意时间点最多只能有一个线程可以连接到内存。当多个线程并发执行时，图中的开关装置能把所有线程的所有内存读/写操作串行化。<br><img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/顺序一致性模型的一种执行效果.png" title="顺序一致性模型的一种执行效果"></p>
<h3 id="同步程序的顺序一致性效果"><a href="#同步程序的顺序一致性效果" class="headerlink" title="同步程序的顺序一致性效果"></a>同步程序的顺序一致性效果</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">SynchronizedExample</span> </span>&#123;</div><div class="line">    <span class="keyword">int</span> a = <span class="number">0</span>;</div><div class="line">    <span class="keyword">boolean</span> flag = <span class="keyword">false</span>;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">writer</span><span class="params">()</span> </span>&#123; <span class="comment">// 获取锁</span></div><div class="line">        a = <span class="number">1</span>;</div><div class="line">        flag = <span class="keyword">true</span>;</div><div class="line">    &#125; <span class="comment">// 释放锁</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">reader</span><span class="params">()</span> </span>&#123; <span class="comment">// 获取锁</span></div><div class="line">        <span class="keyword">if</span> (flag) &#123;</div><div class="line">            <span class="keyword">int</span> i = a;</div><div class="line">        &#125; <span class="comment">// 释放锁</span></div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/两个内存模型中的执行时序对比图.png" title="两个内存模型中的执行时序对比图">
<h3 id="未同步程序的执行特性"><a href="#未同步程序的执行特性" class="headerlink" title="未同步程序的执行特性"></a>未同步程序的执行特性</h3><ul>
<li>对于未同步或未正确同步的多线程程序，JMM只提供最小安全性：线程执行时读取到的值，要么是之前某个线程写入的值，要么是默认值（0，Null，False）。</li>
<li>JMM不保证未同步程序的执行结果与该程序在顺序一致性模型中的执行结果一致。因为如果想要保证执行结果一致，JMM需要禁止大量的处理器和编译器的优化，这对程序的执行性能会产生很大的影响。而且未同步程序在顺序一致性模型中执行时，整体是无序的，其执行结果往往无法预知。</li>
</ul>
<p>未同步程序在两个模型中的执行特性有如下几个差异：</p>
<ol>
<li>顺序一致性模型保证单线程内的操作会按程序的顺序执行，而JMM不保证单线程内的操作会按程序的顺序执行</li>
<li>顺序一致性模型保证所有线程只能看到一致的操作执行顺序，而JMM不保证所有线程能看到一致的操作执行顺序</li>
<li>JMM不保证对64位的long型和double型变量的写操作具有原子性，而顺序一致性模型保证对所有的内存读/写操作都具有原子性。（在一些32位的处理器上，如果要求对64位数据的写操作具有原子性，会有比较大的开销。为了照顾这种处理器，Java语言规范鼓励但不强求JVM对64位的long型变量和double型变量的写操作具有原子性。从JSR-133内存模型开始（即从JDK5开始），仅仅只允许把一个64位long/double型变量的写操作拆分为两个32位的写操作来执行，任意的读操作在JSR-133中都必须具有原子性（即任意读操作必须要在单个读事务中执行。）</li>
</ol>
<blockquote>
<p>数据通过总线在处理器和内存之间传递。每次处理器和内存之间的数据传递都是通过一系列步骤来完成，这些步骤称为总线事务（Bus Transaction）。总线事务包括读事务（Read Transaction）和写事务（Write Transaction）。每个事务会读/写内存中一个或多个物理上连续的字。</p>
</blockquote>
<img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/总线的工作机制.png" title="总线的工作机制">
<p>多个处理器同时向总线发起总线事务，这时总线仲裁（Bus Arbitration）会对竞争做出裁决，总线的这些工作机制可以把所有处理器对内存的访问以串行化的方式来执行。在任意时间点，最多只能有一个处理器可以访问内存。这个特性确保了单个总线事务之中的内存读/写操作具有原子性。</p>
<h2 id="volatile的内存语义"><a href="#volatile的内存语义" class="headerlink" title="volatile的内存语义"></a>volatile的内存语义</h2><h3 id="volatile的特性"><a href="#volatile的特性" class="headerlink" title="volatile的特性"></a>volatile的特性</h3><p>对volatile变量的单个读/写，可看成是使用同一个锁对这些单个读/写操作做了同步。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">VolatileFeaturesExample</span> </span>&#123;</div><div class="line">    <span class="keyword">volatile</span> <span class="keyword">long</span> vl = <span class="number">0L</span>; <span class="comment">// 使用volatile声明64位的long型变量</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">set</span><span class="params">(<span class="keyword">long</span> l)</span> </span>&#123;</div><div class="line">        vl = l; <span class="comment">// 单个volatile变量的写</span></div><div class="line">    &#125;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">getAndIncrement</span> <span class="params">()</span> </span>&#123;</div><div class="line">        vl++; <span class="comment">// 复合（多个）volatile变量的读/写</span></div><div class="line">    &#125;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">long</span> <span class="title">get</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">return</span> vl; <span class="comment">// 单个volatile变量的读</span></div><div class="line">    &#125;</div><div class="line">&#125;</div><div class="line"><span class="comment">/**多个线程分别调用上面程序的3个方法，这个程序在语义上和下面程序等价**/</span></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">VolatileFeaturesExample</span> </span>&#123;</div><div class="line">    <span class="keyword">long</span> vl = <span class="number">0L</span>; <span class="comment">// 64位的long型普通变量</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">set</span><span class="params">(<span class="keyword">long</span> l)</span> </span>&#123; <span class="comment">// 对单个的普通变量的写用同一个锁同步</span></div><div class="line">        vl = l;</div><div class="line">    &#125;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">getAndIncrement</span> <span class="params">()</span> </span>&#123; <span class="comment">// 普通方法调用</span></div><div class="line">        <span class="keyword">long</span> temp = get(); <span class="comment">// 调用已同步的读方法</span></div><div class="line">        temp += <span class="number">1L</span>; <span class="comment">// 普通写操作</span></div><div class="line">        set(temp); <span class="comment">// 调用已同步的写方法</span></div><div class="line">    &#125;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">long</span> <span class="title">get</span><span class="params">()</span> </span>&#123; <span class="comment">// 对单个的普通变量的读用同一个锁同步</span></div><div class="line">        <span class="keyword">return</span> vl;</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>一个volatile变量的单个读/写操作，与一个普通变量的读/写操作都使用同一个锁来同步，它们之间的执行效果相同。volatile变量自身具有下列特性:</p>
<blockquote>
<p>可见性。对一个volatile变量的读，总是能看到（任意线程）对这个volatile变量最后的写入。<br>原子性：对任意单个volatile变量的读/写具有原子性，但类似于volatile++这种复合操作不具有原子性。</p>
</blockquote>
<h3 id="volatile写-读建立的happens-before关系"><a href="#volatile写-读建立的happens-before关系" class="headerlink" title="volatile写-读建立的happens-before关系"></a>volatile写-读建立的happens-before关系</h3><p>从JDK5开始，volatile变量的写-读可以实现线程之间的通信。</p>
<blockquote>
<p>从内存语义的角度来说，volatile的写-读与锁的释放-获取有相同的内存效果：volatile写和锁的释放有相同的内存语义；volatile读与锁的获取有相同的内存语义。</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">VolatileExample</span> </span>&#123;</div><div class="line">    <span class="keyword">int</span> a = <span class="number">0</span>;</div><div class="line">    <span class="keyword">volatile</span> <span class="keyword">boolean</span> flag = <span class="keyword">false</span>;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">writer</span><span class="params">()</span> </span>&#123;</div><div class="line">        a = <span class="number">1</span>;  <span class="comment">// 1</span></div><div class="line">        flag = <span class="keyword">true</span>;    <span class="comment">// 2</span></div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">reader</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">if</span> (flag) &#123; <span class="comment">// 3</span></div><div class="line">            <span class="keyword">int</span> i = a;  <span class="comment">// 4</span></div><div class="line"></div><div class="line">        &#125;</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/happens-before关系.png" title="happens-before关系">
<h3 id="volatile写-读的内存语义"><a href="#volatile写-读的内存语义" class="headerlink" title="volatile写-读的内存语义"></a>volatile写-读的内存语义</h3><blockquote>
<p>volatile写的内存语义：当写一个volatile变量时，JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。<br>volatile读的内存语义：当读一个volatile变量时，JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。</p>
</blockquote>
<p>总结：</p>
<ul>
<li>线程A写一个volatile变量，实质上是线程A向接下来将要读这个volatile变量的某个线程发出了（其对共享变量所做修改的）消息。</li>
<li>线程B读一个volatile变量，实质上是线程B接收了之前某个线程发出的（在写这个volatile变量之前对共享变量所做修改的）消息。</li>
<li>线程A写一个volatile变量，随后线程B读这个volatile变量，这个过程实质上是线程A通过主内存向线程B发送消息。</li>
</ul>
<img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/共享变量的状态示意图.png" title="共享变量的状态示意图">
<h3 id="volatile内存语义的实现（-）"><a href="#volatile内存语义的实现（-）" class="headerlink" title="volatile内存语义的实现（*）"></a>volatile内存语义的实现（*）</h3><p>为了实现volatile内存语义，JMM 会限制编译器重排序和处理器重排序。</p>
<img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/volatile重排序规则表.png" title="volatile重排序规则表">
<p>为了实现volatile的内存语义，编译器在生成字节码时，会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。基于保守策略的JMM内存屏障插入策略：</p>
<ul>
<li>在每个volatile写操作的前面插入一个StoreStore屏障。</li>
<li>在每个volatile写操作的后面插入一个StoreLoad屏障。</li>
<li>在每个volatile读操作的后面插入一个LoadLoad屏障。</li>
<li>在每个volatile读操作的后面插入一个LoadStore屏障。</li>
</ul>
<h3 id="JSR-133增强volatile的内存语义"><a href="#JSR-133增强volatile的内存语义" class="headerlink" title="JSR-133增强volatile的内存语义"></a>JSR-133增强volatile的内存语义</h3><p>JSR-133之前的旧Java内存模型中，不允许volatile变量之间重排序，但允许volatile变量与普通变量重排序。因此，在旧的内存模型中，volatile的写-读没有锁的释放-获取所具有的内存语义。新的 Java 内存模型严格限制编译器和处理器对volatile变量与普通变量的重排序，确保volatile的写-读和锁的释放-获取具有相同的内存语义。</p>
<p>由于volatile仅仅保证对单个volatile变量的读/写具有原子性，而锁的互斥执行的特性可以确保对整个临界区代码的执行具有原子性。在功能上，锁比volatile更强大；在可伸缩性和执行性能上，volatile更有优势。</p>
<p>参考：<a href="https://www.ibm.com/developerworks/cn/java/j-jtp06197.html" target="_blank" rel="noopener">《Brian Goetz：Java理论与实践：正确使用Volatile变量》</a></p>
<h2 id="锁的内存语义"><a href="#锁的内存语义" class="headerlink" title="锁的内存语义"></a>锁的内存语义</h2><h3 id="锁的释放-获取建立的happens-before关系"><a href="#锁的释放-获取建立的happens-before关系" class="headerlink" title="锁的释放-获取建立的happens-before关系"></a>锁的释放-获取建立的happens-before关系</h3><p>锁释放-获取的示例代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">MonitorExample</span> </span>&#123;</div><div class="line">    <span class="keyword">int</span> a = <span class="number">0</span>;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">writer</span><span class="params">()</span> </span>&#123;<span class="comment">// 1</span></div><div class="line">        a++;<span class="comment">// 2</span></div><div class="line">    &#125;<span class="comment">// 3</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">reader</span><span class="params">()</span> </span>&#123;<span class="comment">// 4</span></div><div class="line">        <span class="keyword">int</span> i = a;<span class="comment">// 5</span></div><div class="line">    &#125;<span class="comment">// 6</span></div><div class="line">&#125;</div></pre></td></tr></table></figure>
<img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/happens-before关系图.png" title="happens-before关系图">
<p>线程A在释放锁之前所有可见的共享变量，在线程B获取同一个锁之后，将立刻变得对B线程可见。</p>
<h3 id="锁的释放和获取的内存语义"><a href="#锁的释放和获取的内存语义" class="headerlink" title="锁的释放和获取的内存语义"></a>锁的释放和获取的内存语义</h3><p><strong>当线程释放锁时，JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。</strong><br><strong>当线程获取锁时，JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。</strong><br><img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/锁获取的状态示意图.png" title="锁获取的状态示意图"></p>
<blockquote>
<p>锁释放与volatile写有相同的内存语义；锁获取与volatile读有相同的内存语义。</p>
</blockquote>
<p>锁释放和锁获取的内存语义总结：</p>
<ul>
<li>线程A释放一个锁，实质上是线程A向接下来将要获取这个锁的某个线程发出了（线程A对共享变量所做修改的）消息。</li>
<li>线程B获取一个锁，实质上是线程B接收了之前某个线程发出的（在释放这个锁之前对共享变量所做修改的）消息。</li>
<li>线程A释放锁，随后线程B获取这个锁，这个过程实质上是线程A通过主内存向线程B发送消息。</li>
</ul>
<h3 id="锁内存语义的实现（-）"><a href="#锁内存语义的实现（-）" class="headerlink" title="锁内存语义的实现（*）"></a>锁内存语义的实现（*）</h3><p>ReentrantLock的实现依赖于Java同步器框架AbstractQueuedSynchronizer（本文简称之为AQS）。AQS使用一个整型的volatile变量（命名为state）来维护同步状态，这个volatile变量是ReentrantLock内存语义实现的关键。</p>
<p>公平锁和非公平锁的内存语义总结：</p>
<ul>
<li>公平锁和非公平锁释放时，最后都要写一个volatile变量state。</li>
<li>公平锁获取时，首先会去读volatile变量。</li>
<li>非公平锁获取时，首先会用CAS更新volatile变量，这个操作同时具有volatile读和volatile写的内存语义。</li>
</ul>
<p>锁释放-获取的内存语义的实现至少有下面两种方式。</p>
<ol>
<li>利用volatile变量的写-读所具有的内存语义。</li>
<li>利用CAS所附带的volatile读和volatile写的内存语义。</li>
</ol>
<p>从Pentium 4、Intel Xeon及P6处理器开始，Intel使用缓存锁定（Cache Locking）来保证指令执行的原子性。缓存锁定将大大降低lock前缀指令的执行开销。</p>
<h3 id="concurrent-包的实现"><a href="#concurrent-包的实现" class="headerlink" title="concurrent 包的实现"></a>concurrent 包的实现</h3><p>volatile变量的读/写和CAS可以实现线程之间的通信，concurrent包实现的通用化的实现模式：</p>
<ol>
<li>首先，声明共享变量为volatile。</li>
<li>然后，使用CAS的原子条件更新来实现线程之间的同步。</li>
<li>同时，配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。</li>
</ol>
<p>AQS，非阻塞数据结构和原子变量类这些concurrent包中的基础类都是使用这种模式来实现的，而concurrent包中的高层类又是依赖于这些基础类来实现。</p>
<img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/concurrent包的实现示意图.png" title="concurrent包的实现示意图">
<h2 id="final域的内存语义"><a href="#final域的内存语义" class="headerlink" title="final域的内存语义"></a>final域的内存语义</h2><h3 id="final域的重排序规则"><a href="#final域的重排序规则" class="headerlink" title="final域的重排序规则"></a>final域的重排序规则</h3><p>对于final域，编译器和处理器要遵守两个重排序规则：</p>
<ol>
<li>在构造函数内对一个final域的写入，与随后把这个被构造对象的引用赋值给一个引用变量，这两个操作之间不能重排序。</li>
<li>初次读一个包含final域的对象的引用，与随后初次读这个final域，这两个操作之间不能重排序。</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">FinalExample</span> </span>&#123;</div><div class="line">    <span class="keyword">int</span> i;<span class="comment">// 普通变量</span></div><div class="line">    <span class="keyword">final</span> <span class="keyword">int</span> j;<span class="comment">// final变量</span></div><div class="line">    <span class="keyword">static</span> FinalExample obj;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="title">FinalExample</span> <span class="params">()</span> </span>&#123;<span class="comment">// 构造函数</span></div><div class="line">        i = <span class="number">1</span>;<span class="comment">// 写普通域</span></div><div class="line">        j = <span class="number">2</span>;<span class="comment">// 写final域</span></div><div class="line">    &#125;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">writer</span> <span class="params">()</span> </span>&#123;<span class="comment">// 写线程A执行</span></div><div class="line">        obj = <span class="keyword">new</span> FinalExample ();</div><div class="line">    &#125;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">reader</span> <span class="params">()</span> </span>&#123;<span class="comment">// 读线程B执行</span></div><div class="line">        FinalExample object = obj; <span class="comment">// 读对象引用</span></div><div class="line">        <span class="keyword">int</span> a = object.i;<span class="comment">// 读普通域</span></div><div class="line">        <span class="keyword">int</span> b = object.j;<span class="comment">// 读final域</span></div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h3 id="写final域的重排序规则"><a href="#写final域的重排序规则" class="headerlink" title="写final域的重排序规则"></a>写final域的重排序规则</h3><p>写final域的重排序规则禁止把final域的写重排序到构造函数之外：</p>
<ol>
<li>JMM禁止编译器把final域的写重排序到构造函数之外。</li>
<li>编译器会在final域的写之后，构造函数return之前，插入一个StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构造函数之外。</li>
</ol>
<img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/线程执行时序图.png" title="线程执行时序图">
<p>写final域的重排序规则可以确保：在对象引用为任意线程可见之前，对象的final域已经被正确初始化过了，而普通域不具有这个保障。</p>
<h3 id="读final域的重排序规则"><a href="#读final域的重排序规则" class="headerlink" title="读final域的重排序规则"></a>读final域的重排序规则</h3><p>读final域的重排序规则是，在一个线程中，初次读对象引用与初次读该对象包含的final域，JMM禁止处理器重排序这两个操作。</p>
<img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/线程执行时序图2.png" title="线程执行时序图">
<p>读final域的重排序规则可以确保：在读一个对象的final域之前，一定会先读包含这个final域的对象的引用。</p>
<h3 id="final域为引用类型"><a href="#final域为引用类型" class="headerlink" title="final域为引用类型"></a>final域为引用类型</h3><p>对于引用类型，写final域的重排序规则对编译器和处理器增加了如下约束：在构造函数内对一个final引用的对象的成员域的写入，与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量，这两个操作之间不能重排序。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">FinalReferenceExample</span> </span>&#123;</div><div class="line">    <span class="keyword">final</span> <span class="keyword">int</span>[] intArray; <span class="comment">// final是引用类型</span></div><div class="line">    <span class="keyword">static</span> FinalReferenceExample obj;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="title">FinalReferenceExample</span> <span class="params">()</span> </span>&#123; <span class="comment">// 构造函数</span></div><div class="line">        intArray = <span class="keyword">new</span> <span class="keyword">int</span>[<span class="number">1</span>]; <span class="comment">// 1</span></div><div class="line">        intArray[<span class="number">0</span>] = <span class="number">1</span>; <span class="comment">// 2</span></div><div class="line">    &#125;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">writerOne</span> <span class="params">()</span> </span>&#123; <span class="comment">// 写线程A执行</span></div><div class="line">        obj = <span class="keyword">new</span> FinalReferenceExample (); <span class="comment">// 3</span></div><div class="line">    &#125;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">writerTwo</span> <span class="params">()</span> </span>&#123; <span class="comment">// 写线程B执行</span></div><div class="line">        obj.intArray[<span class="number">0</span>] = <span class="number">2</span>; <span class="comment">// 4</span></div><div class="line">    &#125;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">reader</span> <span class="params">()</span> </span>&#123; <span class="comment">// 读线程C执行</span></div><div class="line">        <span class="keyword">if</span> (obj != <span class="keyword">null</span>) &#123; <span class="comment">// 5</span></div><div class="line">            <span class="keyword">int</span> temp1 = obj.intArray[<span class="number">0</span>]; <span class="comment">// 6</span></div><div class="line">        &#125;</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>JMM可以确保读线程C至少能看到写线程A在构造函数中对final引用对象的成员域的写入。即C至少能看到数组下标0的值为1。而写线程B对数组元素的写入，读线程C可能看得到，也可能看不到。JMM不保证线程B的写入对读线程C可见，因为写线程B和读线程C之间存在数据竞争，此时的执行结果不可预知。</p>
<p>要确保读线程C看到写线程B对数组元素的写入，写线程B和读线程C之间需要使用同步原语（lock或volatile）来确保内存可见性。</p>
<h3 id="final引用不能从构造函数内“溢出”"><a href="#final引用不能从构造函数内“溢出”" class="headerlink" title="final引用不能从构造函数内“溢出”"></a>final引用不能从构造函数内“溢出”</h3><p>写final域的重排序规则可以确保：在引用变量为任意线程可见之前，该引用变量指向的对象的final域已经在构造函数中被正确初始化过了。要得到这个效果，还需要：<strong>在构造函数内部，不能让这个被构造对象的引用为其他线程所见，也就是对象引用不能在构造函数中“逸出”。</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">FinalReferenceEscapeExample</span> </span>&#123;</div><div class="line">    <span class="keyword">final</span> <span class="keyword">int</span> i;</div><div class="line">    <span class="keyword">static</span> FinalReferenceEscapeExample obj;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="title">FinalReferenceEscapeExample</span> <span class="params">()</span> </span>&#123;</div><div class="line">        i = <span class="number">1</span>; <span class="comment">// 1写final域</span></div><div class="line">        obj = <span class="keyword">this</span>; <span class="comment">// 2 this引用在此"逸出"</span></div><div class="line">    &#125;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">writer</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">new</span> FinalReferenceEscapeExample ();</div><div class="line">    &#125;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">reader</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">if</span> (obj != <span class="keyword">null</span>) &#123; <span class="comment">// 3</span></div><div class="line">            <span class="keyword">int</span> temp = obj.i; <span class="comment">// 4</span></div><div class="line">        &#125;</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/多线程执行时序图.png" title="多线程执行时序图">
<p>在构造函数返回前，被构造对象的引用不能为其他线程所见，因为此时的final域可能还没有被初始化。在构造函数返回后，任意线程都将保证能看到final域正确初始化之后的值。</p>
<h3 id="final语义在处理器中的实现"><a href="#final语义在处理器中的实现" class="headerlink" title="final语义在处理器中的实现"></a>final语义在处理器中的实现</h3><p>写final域的重排序规则会要求编译器在final域的写之后，构造函数return之前插入一个StoreStore障屏。读final域的重排序规则要求编译器在读final域的操作前面插入一个LoadLoad屏障。</p>
<p>由于X86处理器不会对写-写操作做重排序，也不会对存在间接依赖关系的操作做重排序，即在X86处理器中，final域的读/写不会插入任何内存屏障！</p>
<h3 id="JSR-133增强final语义"><a href="#JSR-133增强final语义" class="headerlink" title="JSR-133增强final语义"></a>JSR-133增强final语义</h3><p>JSR-133专家组增强了final的语义。通过为final域增加写和读重排序规则，可以为Java程序员提供初始化安全保证：只要对象是正确构造的<strong>（被构造对象的引用在构造函数中没有“逸出”）</strong>，那么不需要使用同步（指lock和volatile的使用）就可以保证任意线程都能看到这个final域在构造函数中被初始化之后的值。</p>
<h2 id="happens-before"><a href="#happens-before" class="headerlink" title="happens-before"></a>happens-before</h2><p>happens-before是JMM最核心的概念，是理解JMM的关键。</p>
<h3 id="JMM设计"><a href="#JMM设计" class="headerlink" title="JMM设计"></a>JMM设计</h3><p>JMM把happens-before要求禁止的重排序分为<strong>会改变程序结果和不会改变程序结果</strong>两类。JMM对这两种不同性质重排序采取不同策略：</p>
<ol>
<li>会改变程序结果的，JMM要求编译器和处理器必须禁止这种重排序。</li>
<li>不会改变程序结果的，JMM允许重排序。</li>
</ol>
<h3 id="JSR-133对happens-before的定义"><a href="#JSR-133对happens-before的定义" class="headerlink" title="JSR-133对happens-before的定义"></a>JSR-133对happens-before的定义</h3><ol>
<li>如果一个操作happens-before另一个操作，那么第一个操作的执行结果将对第二个操作可见，而且第一个操作的执行顺序排在第二个操作之前。<blockquote>
<p>这是JMM对程序员的保证，如果A happens-before B，A操作的结果将对B可见，且A的执行顺序排在B之前。</p>
</blockquote>
</li>
<li>两个操作之间存在happens-before关系，并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果，与按happens-before关系来执行的结果一致，那么这种重排序并不非法（即JMM允许这种重排序）。<blockquote>
<p>这是JMM对编译器和处理器重排序的约束原则。只要不改变程序的执行结果（指的是单线程程序和正确同步的多线程程序），编译器和处理器怎么优化都行。</p>
</blockquote>
</li>
</ol>
<h3 id="happens-before-规则-1"><a href="#happens-before-规则-1" class="headerlink" title="happens-before 规则"></a>happens-before 规则</h3><ol>
<li>程序顺序规则：一个线程中的每个操作，happens-before于该线程中的任意后续操作。</li>
<li>监视器锁规则：对一个锁的解锁，happens-before于随后对这个锁的加锁。</li>
<li>volatile变量规则：对一个volatile域的写，happens-before于任意后续对这个volatile域的读。</li>
<li>传递性：如果A happens-before B，且B happens-before C，那么A happens-before C。</li>
<li>start()规则：如果线程A执行操作ThreadB.start()（启动线程B），那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。</li>
<li>join()规则：如果线程A执行操作ThreadB.join()并成功返回，那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。</li>
</ol>
<h2 id="双重检查锁定与延迟初始化"><a href="#双重检查锁定与延迟初始化" class="headerlink" title="双重检查锁定与延迟初始化"></a>双重检查锁定与延迟初始化</h2><p>在Java多线程程序中，有时候需要采用延迟初始化来降低初始化类和创建对象的开销。双重检查锁定是常见的延迟初始化技术</p>
<h3 id="双重检查锁定的由来"><a href="#双重检查锁定的由来" class="headerlink" title="双重检查锁定的由来"></a>双重检查锁定的由来</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//A线程执行代码1的同时，B线程执行代码2。此时，线程A可能会看到instance引用的对象还没有完成初始化，原因见下节</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UnsafeLazyInitialization</span> </span>&#123;</div><div class="line">    <span class="keyword">private</span> <span class="keyword">static</span> Instance instance;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Instance <span class="title">getInstance</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">if</span> (instance == <span class="keyword">null</span>) <span class="comment">// 1：A线程执行</span></div><div class="line">            instance = <span class="keyword">new</span> Instance(); <span class="comment">// 2：B线程执行</span></div><div class="line">        <span class="keyword">return</span> instance;</div><div class="line">    &#125;</div><div class="line">    <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Instance</span></span>&#123;&#125;</div><div class="line">&#125;</div><div class="line"></div><div class="line"><span class="comment">//synchronized将导致性能开销，如果getInstance()方法被多个线程频繁的调用，将会导致程序执行性能的下降。</span></div><div class="line"><span class="class"><span class="keyword">class</span> <span class="title">SafeLazyInitialization</span> </span>&#123;</div><div class="line">    <span class="keyword">private</span> <span class="keyword">static</span> Instance instance;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">static</span> Instance <span class="title">getInstance</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">if</span> (instance == <span class="keyword">null</span>)</div><div class="line">            instance = <span class="keyword">new</span> Instance();</div><div class="line">        <span class="keyword">return</span> instance;</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>在早期的JVM中，synchronized（甚至是无竞争的synchronized）存在巨大的性能开销。因此，人们想出了一个“聪明”的技巧：双重检查锁定（Double-Checked Locking）。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DoubleCheckedLocking</span> </span>&#123; <span class="comment">// 1</span></div><div class="line">    <span class="keyword">private</span> <span class="keyword">static</span> Instance instance; <span class="comment">// 2</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Instance <span class="title">getInstance</span><span class="params">()</span> </span>&#123; <span class="comment">// 3</span></div><div class="line">        <span class="keyword">if</span> (instance == <span class="keyword">null</span>) &#123; <span class="comment">// 4:第一次检查</span></div><div class="line">            <span class="keyword">synchronized</span> (DoubleCheckedLocking.class) &#123; <span class="comment">// 5:加锁</span></div><div class="line">                <span class="keyword">if</span> (instance == <span class="keyword">null</span>) <span class="comment">// 6:第二次检查</span></div><div class="line">                    instance = <span class="keyword">new</span> Instance(); <span class="comment">// 7:问题的根源出在这里</span></div><div class="line">            &#125; <span class="comment">// 8</span></div><div class="line">        &#125; <span class="comment">// 9</span></div><div class="line">        <span class="keyword">return</span> instance; <span class="comment">// 10</span></div><div class="line">    &#125; <span class="comment">// 11</span></div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>双重检查锁定看起来似乎很完美，但这是一个错误的优化！<strong>在线程执行到第4行，代码读取到instance不为null时，instance引用的对象有可能还没有完成初始化。</strong></p>
<h3 id="问题根源"><a href="#问题根源" class="headerlink" title="问题根源"></a>问题根源</h3><p><code>instance=new Singleton();</code>创建了一个对象，这一行代码可分解为如下的3行伪代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">memory = allocate();　　<span class="comment">// 1：分配对象的内存空间</span></div><div class="line">ctorInstance(memory);　<span class="comment">// 2：初始化对象</span></div><div class="line">instance = memory;　　<span class="comment">// 3：设置instance指向刚分配的内存地址</span></div></pre></td></tr></table></figure>
<p>在一些JIT编译器上，2和3之间可能会被重排序，即将instance先指向了某块内存，但那块内存并没有被初始化：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">memory = allocate();　　<span class="comment">// 1：分配对象的内存空间</span></div><div class="line">instance = memory;　　<span class="comment">// 3：设置instance指向刚分配的内存地址</span></div><div class="line"><span class="comment">// 注意，此时对象还没有被初始化！</span></div><div class="line">ctorInstance(memory);　<span class="comment">// 2：初始化对象</span></div></pre></td></tr></table></figure>
<p>因此，DoubleCheckedLocking示例代码的第7行（instance=new Singleton();）如果<br>发生重排序，另一个并发执行的线程B就有可能在第4行判断instance不为null。线程B接下来将访问instance所引用的对象，但此时这个对象可能还没有被A线程初始化！</p>
<img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/多线程执行时序表.png" title="多线程执行时序表">
<p>解决办法：</p>
<ol>
<li>不允许2和3重排序。</li>
<li>允许2和3重排序，但不允许其他线程“看到”这个重排序。</li>
</ol>
<h3 id="基于volatile的解决方案"><a href="#基于volatile的解决方案" class="headerlink" title="基于volatile的解决方案"></a>基于volatile的解决方案</h3><p>把instance声明为volatile型，即可实现线程安全的延迟初始化:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SafeDoubleCheckedLocking</span> </span>&#123;</div><div class="line">    <span class="comment">//注意加了volatile</span></div><div class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">static</span> Instance instance;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Instance <span class="title">getInstance</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">if</span> (instance == <span class="keyword">null</span>) &#123;</div><div class="line">            <span class="keyword">synchronized</span> (SafeDoubleCheckedLocking.class) &#123;</div><div class="line">                <span class="keyword">if</span> (instance == <span class="keyword">null</span>)</div><div class="line">                    instance = <span class="keyword">new</span> Instance(); <span class="comment">// instance为volatile，现在没问题了</span></div><div class="line">            &#125;</div><div class="line">        &#125;</div><div class="line">        <span class="keyword">return</span> instance;</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>该方案需JDK5+，当声明对象的引用为volatile后，上节3行伪代码中的2和3之间的重排序，在多线程环境中将会被禁止。</p>
<h3 id="基于类初始化的解决方案"><a href="#基于类初始化的解决方案" class="headerlink" title="基于类初始化的解决方案"></a>基于类初始化的解决方案</h3><p>JVM在类的初始化阶段（即在Class被加载后，且被线程使用之前），会执行类的初始化。在执行类的初始化期间，JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">InstanceFactory</span> </span>&#123;</div><div class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">InstanceHolder</span> </span>&#123;</div><div class="line">        <span class="keyword">public</span> <span class="keyword">static</span> Instance instance = <span class="keyword">new</span> Instance();</div><div class="line">    &#125;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Instance <span class="title">getInstance</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">return</span> InstanceHolder.instance ;　　<span class="comment">// 这里将导致InstanceHolder类被初始化</span></div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/两个线程并发执行的示意图.png" title="两个线程并发执行的示意图">
<blockquote>
<p>该方法允许重排序，但不允许非构造线程（这里指线程B）“看到”这个重排序。</p>
</blockquote>
<p>初始化一个类，包括执行这个类的静态初始化和初始化在类中声明的静态字段。类初始化发生在下列几种情况：</p>
<ol>
<li>T是一个类，而且一个T类型的实例被创建。</li>
<li>T是一个类，且T中声明的一个静态方法被调用。</li>
<li>T中声明的一个静态字段被赋值。</li>
<li>T中声明的一个静态字段被使用，而且这个字段不是一个常量字段。</li>
<li>T是一个顶级类（Top Level Class，Java语言规范的§7.6），而且一个断言语句嵌套在T内部被执行。</li>
</ol>
<p>对比基于volatile的双重检查锁定的方案和基于类初始化的方案：</p>
<ul>
<li>基于类初始化的方案的实现代码更简洁</li>
<li>基于volatile的双重检查锁定的方案除了可以对静态字段实现延迟初始化外，还可以对实例字段实现延迟初始化。</li>
</ul>
<p>字段延迟初始化降低了初始化类或创建实例的开销，但增加了访问被延迟初始化的字段的开销。</p>
<ol>
<li>实例字段使用线程安全的延迟初始化<ul>
<li>基于volatile的延迟初始化的方案</li>
</ul>
</li>
<li>对静态字段使用线程安全的延迟初始化<ul>
<li>基于类初始化的方案</li>
</ul>
</li>
</ol>
<h2 id="Java内存模型综述"><a href="#Java内存模型综述" class="headerlink" title="Java内存模型综述"></a>Java内存模型综述</h2><h3 id="处理器内存模型"><a href="#处理器内存模型" class="headerlink" title="处理器内存模型"></a>处理器内存模型</h3><img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/处理器内存模型的特征表.png" title="处理器内存模型的特征表">
<p>常见的处理器内存模型比JMM要弱，Java编译器在生成字节码时，会在执行指令序列的适当位置插入内存屏障来限制处理器的重排序。同时，由于各种处理器内存模型的强弱不同，为了在不同的处理器平台向程序员展示一个一致的内存模型，JMM在不同的处理器中需要插入的内存屏障的数量和种类也不相同。</p>
<img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/JMM插入内存屏障的示意图.png" title="JMM插入内存屏障的示意图">
<h3 id="各种内存模型之间的关系"><a href="#各种内存模型之间的关系" class="headerlink" title="各种内存模型之间的关系"></a>各种内存模型之间的关系</h3><p>JMM是一个语言级的内存模型，处理器内存模型是硬件级的内存模型，顺序一致性内存模型是一个理论参考模型。</p>
<img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/各种CPU内存模型的强弱对比示意图.png" title="各种CPU内存模型的强弱对比示意图">
<h3 id="JMM的内存可见性保证"><a href="#JMM的内存可见性保证" class="headerlink" title="JMM的内存可见性保证"></a>JMM的内存可见性保证</h3><p>Java程序的内存可见性保证可以分为下列3类：</p>
<ol>
<li>单线程程序。单线程程序不会出现内存可见性问题。编译器、runtime和处理器会共同<strong>确保单线程程序的执行结果与该程序在顺序一致性模型中的执行结果相同。</strong></li>
<li>正确同步的多线程程序。<strong>正确同步的多线程程序的执行将具有顺序一致性（程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同）。</strong>这是JMM关注的重点，JMM通过限制编译器和处理器的重排序来为程序员提供内存可见性保证。</li>
<li>未同步/未正确同步的多线程程序。JMM为它们提供了最小安全性保障：线程执行时读取到的值，要么是之前某个线程写入的值，要么是默认值（0、null、false；最小安全性保证线程读取到的值不会无中生有的冒出来，但并不保证线程读取到的值一定是正确的。)</li>
</ol>
<img src="/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/3类程序的执行结果的对比图.png" title="3类程序的执行结果的对比图">
<h3 id="JSR-133对旧内存模型的修补"><a href="#JSR-133对旧内存模型的修补" class="headerlink" title="JSR-133对旧内存模型的修补"></a>JSR-133对旧内存模型的修补</h3><ol>
<li>增强volatile的内存语义。旧内存模型允许volatile变量与普通变量重排序，新MM严格限制volatile变量与普通变量的重排序，使volatile的写-读和锁的释放-获取具有相同的内存语义。</li>
<li>增强final的内存语义。在旧内存模型中，多次读取同一个final变量的值可能会不相同。新MM增加了两个重排序规则。在保证final引用不会从构造函数内逸出的情况下，final具有了初始化安全性。</li>
</ol>

      
    </div>
    
    
    

    

    
      <div>
        <div style="padding: 10px 0; margin: 20px auto; width: 90%; text-align: center;">
  <div>请我喝杯咖啡吧☕~</div>
  <button id="rewardButton" disable="enable" onclick="var qr = document.getElementById('QR'); if (qr.style.display === 'none') {qr.style.display='block';} else {qr.style.display='none'}">
    <span>打赏</span>
  </button>
  <div id="QR" style="display: none;">

    
      <div id="wechat" style="display: inline-block">
        <img id="wechat_qr" src="/images/wechatpay.jpg" alt="Fighter. 微信支付"/>
        <p>微信支付</p>
      </div>
    

    
      <div id="alipay" style="display: inline-block">
        <img id="alipay_qr" src="/images/alipay.jpg" alt="Fighter. 支付宝"/>
        <p>支付宝</p>
      </div>
    

    

  </div>
</div>

      </div>
    

    
      <div>
        <ul class="post-copyright">
  <li class="post-copyright-author">
    <strong>本文作者：</strong>
    Fighter.
  </li>
  <li class="post-copyright-link">
    <strong>本文链接：</strong>
    <a href="http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/" title="（三）Java内存模型">http://fighterhit.github.io/2018/03/04/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch3-Java内存模型/</a>
  </li>
  <li class="post-copyright-license">
    <strong>版权声明： </strong>
    本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/3.0/" rel="external nofollow" target="_blank">CC BY-NC-SA 3.0</a> 许可协议。转载请注明出处！
  </li>
</ul>

      </div>
    

    <footer class="post-footer">
      
        <div class="post-tags">
          
            <a href="/tags/Java/" rel="tag"># Java</a>
          
            <a href="/tags/并发/" rel="tag"># 并发</a>
          
        </div>
      

      
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/2018/03/02/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch2-Java并发机制的底层实现原理/" rel="next" title="（二）Java并发机制的底层实现原理">
                <i class="fa fa-chevron-left"></i> （二）Java并发机制的底层实现原理
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/2018/03/14/Java_Notes/JavaConcurrence/TheArtOfJavaConcurrency/Ch4-Java并发编程基础/" rel="prev" title="（四）Java并发编程基础">
                （四）Java并发编程基础 <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>



    <div class="post-spread">
      
        <!-- JiaThis Button BEGIN -->
<div class="jiathis_style">
<span class="jiathis_txt">分享到：</span>
<a class="jiathis_button_fav">收藏夹</a>
<a class="jiathis_button_tsina">新浪微博</a>
<a class="jiathis_button_qzone">QQ空间</a>
<a class="jiathis_button_weixin">微信</a>
<a class="jiathis_button_cqq">腾讯QQ</a>
<a class="jiathis_button_copy">复制网址</a>
<a class="jiathis_button_share">一键分享</a>

<a href="http://www.jiathis.com/share?uid=2140465" class="jiathis jiathis_txt jiathis_separator jtico jtico_jiathis" target="_blank">更多</a>
<a class="jiathis_counter_style"></a>
</div>
<script type="text/javascript" >
var jiathis_config={
  data_track_clickback:true,
  summary:"",
  shortUrl:false,
  hideMore:false
}
</script>
<script type="text/javascript" src="http://v3.jiathis.com/code/jia.js?uid=" charset="utf-8"></script>
<!-- JiaThis Button END -->
      
    </div>
  </div>


          </div>
          


          
  
    <div class="comments" id="comments">
      <div id="lv-container" data-id="city" data-uid="MTAyMC8zMjY0Ni85MjA3"></div>
    </div>
  


        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview-wrap">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview-wrap sidebar-panel">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
            
              <img class="site-author-image" itemprop="image"
                src="/images/avatar.jpg"
                alt="Fighter." />
            
              <p class="site-author-name" itemprop="name">Fighter.</p>
              <p class="site-description motion-element" itemprop="description">学习 | 分享 | 交流 | 进步</p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/archives/">
              
                  <span class="site-state-item-count">26</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/categories/index.html">
                  <span class="site-state-item-count">6</span>
                  <span class="site-state-item-name">分类</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-tags">
                <a href="/tags/index.html">
                  <span class="site-state-item-count">28</span>
                  <span class="site-state-item-name">标签</span>
                </a>
              </div>
            

          </nav>

          

          <div class="links-of-author motion-element">
            
              
                <span class="links-of-author-item">
                  <a href="https://github.com/fighterhit" target="_blank" title="GitHub">
                    
                      <i class="fa fa-fw fa-github"></i>GitHub</a>
                </span>
              
                <span class="links-of-author-item">
                  <a href="https://weibo.com/fighterhit" target="_blank" title="Weibo">
                    
                      <i class="fa fa-fw fa-weibo"></i>Weibo</a>
                </span>
              
            
          </div>

          
          

          
          

          

        </div>
      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#Java-内存模型的基础"><span class="nav-number">1.</span> <span class="nav-text">Java 内存模型的基础</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#并发编程模型的两个关键问题"><span class="nav-number">1.1.</span> <span class="nav-text">并发编程模型的两个关键问题</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Java-内存模型的抽象结构"><span class="nav-number">1.2.</span> <span class="nav-text">Java 内存模型的抽象结构</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#从源代码到指令序列的重排序"><span class="nav-number">1.3.</span> <span class="nav-text">从源代码到指令序列的重排序</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#并发编程模型的分类"><span class="nav-number">1.4.</span> <span class="nav-text">并发编程模型的分类</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#happens-before-规则"><span class="nav-number">1.5.</span> <span class="nav-text">happens-before 规则</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#重排序"><span class="nav-number">2.</span> <span class="nav-text">重排序</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#数据依赖性"><span class="nav-number">2.1.</span> <span class="nav-text">数据依赖性</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#as-if-serial语义"><span class="nav-number">2.2.</span> <span class="nav-text">as-if-serial语义</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#程序顺序规则"><span class="nav-number">2.3.</span> <span class="nav-text">程序顺序规则</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#重排序对多线程的影响"><span class="nav-number">2.4.</span> <span class="nav-text">重排序对多线程的影响</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#顺序一致性"><span class="nav-number">3.</span> <span class="nav-text">顺序一致性</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#数据竞争与顺序一致性"><span class="nav-number">3.1.</span> <span class="nav-text">数据竞争与顺序一致性</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#顺序一致性内存模型"><span class="nav-number">3.2.</span> <span class="nav-text">顺序一致性内存模型</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#同步程序的顺序一致性效果"><span class="nav-number">3.3.</span> <span class="nav-text">同步程序的顺序一致性效果</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#未同步程序的执行特性"><span class="nav-number">3.4.</span> <span class="nav-text">未同步程序的执行特性</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#volatile的内存语义"><span class="nav-number">4.</span> <span class="nav-text">volatile的内存语义</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#volatile的特性"><span class="nav-number">4.1.</span> <span class="nav-text">volatile的特性</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#volatile写-读建立的happens-before关系"><span class="nav-number">4.2.</span> <span class="nav-text">volatile写-读建立的happens-before关系</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#volatile写-读的内存语义"><span class="nav-number">4.3.</span> <span class="nav-text">volatile写-读的内存语义</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#volatile内存语义的实现（-）"><span class="nav-number">4.4.</span> <span class="nav-text">volatile内存语义的实现（*）</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#JSR-133增强volatile的内存语义"><span class="nav-number">4.5.</span> <span class="nav-text">JSR-133增强volatile的内存语义</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#锁的内存语义"><span class="nav-number">5.</span> <span class="nav-text">锁的内存语义</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#锁的释放-获取建立的happens-before关系"><span class="nav-number">5.1.</span> <span class="nav-text">锁的释放-获取建立的happens-before关系</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#锁的释放和获取的内存语义"><span class="nav-number">5.2.</span> <span class="nav-text">锁的释放和获取的内存语义</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#锁内存语义的实现（-）"><span class="nav-number">5.3.</span> <span class="nav-text">锁内存语义的实现（*）</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#concurrent-包的实现"><span class="nav-number">5.4.</span> <span class="nav-text">concurrent 包的实现</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#final域的内存语义"><span class="nav-number">6.</span> <span class="nav-text">final域的内存语义</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#final域的重排序规则"><span class="nav-number">6.1.</span> <span class="nav-text">final域的重排序规则</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#写final域的重排序规则"><span class="nav-number">6.2.</span> <span class="nav-text">写final域的重排序规则</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#读final域的重排序规则"><span class="nav-number">6.3.</span> <span class="nav-text">读final域的重排序规则</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#final域为引用类型"><span class="nav-number">6.4.</span> <span class="nav-text">final域为引用类型</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#final引用不能从构造函数内“溢出”"><span class="nav-number">6.5.</span> <span class="nav-text">final引用不能从构造函数内“溢出”</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#final语义在处理器中的实现"><span class="nav-number">6.6.</span> <span class="nav-text">final语义在处理器中的实现</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#JSR-133增强final语义"><span class="nav-number">6.7.</span> <span class="nav-text">JSR-133增强final语义</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#happens-before"><span class="nav-number">7.</span> <span class="nav-text">happens-before</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#JMM设计"><span class="nav-number">7.1.</span> <span class="nav-text">JMM设计</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#JSR-133对happens-before的定义"><span class="nav-number">7.2.</span> <span class="nav-text">JSR-133对happens-before的定义</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#happens-before-规则-1"><span class="nav-number">7.3.</span> <span class="nav-text">happens-before 规则</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#双重检查锁定与延迟初始化"><span class="nav-number">8.</span> <span class="nav-text">双重检查锁定与延迟初始化</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#双重检查锁定的由来"><span class="nav-number">8.1.</span> <span class="nav-text">双重检查锁定的由来</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#问题根源"><span class="nav-number">8.2.</span> <span class="nav-text">问题根源</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#基于volatile的解决方案"><span class="nav-number">8.3.</span> <span class="nav-text">基于volatile的解决方案</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#基于类初始化的解决方案"><span class="nav-number">8.4.</span> <span class="nav-text">基于类初始化的解决方案</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Java内存模型综述"><span class="nav-number">9.</span> <span class="nav-text">Java内存模型综述</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#处理器内存模型"><span class="nav-number">9.1.</span> <span class="nav-text">处理器内存模型</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#各种内存模型之间的关系"><span class="nav-number">9.2.</span> <span class="nav-text">各种内存模型之间的关系</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#JMM的内存可见性保证"><span class="nav-number">9.3.</span> <span class="nav-text">JMM的内存可见性保证</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#JSR-133对旧内存模型的修补"><span class="nav-number">9.4.</span> <span class="nav-text">JSR-133对旧内存模型的修补</span></a></li></ol></li></ol></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      
      
    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; 2016 - <span itemprop="copyrightYear">2018</span>
  <span class="with-love">
    <i class="fa fa-home"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">Fighter.</span>

  
</div>









        
<div class="busuanzi-count">
  <!--<script async src="https://dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js"></script>-->
  <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

  
    <span class="site-uv">
      <i class="fa fa-user"></i>
      <span class="busuanzi-value" id="busuanzi_value_site_uv"></span>
      
    </span>
  

  
    <span class="site-pv">
      <i class="fa fa-eye"></i>
      <span class="busuanzi-value" id="busuanzi_value_site_pv"></span>
      
    </span>
  

    <span class="site-pv">&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;
    Hosted by <a target="_blank" href="https://github.com/">GitHub Pages</a>
    </span>

</div>







        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>

  
  <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>

  
  <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>

  
  <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>

  
  <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>

  
  <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.3"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.3"></script>



  
  


  <script type="text/javascript" src="/js/src/affix.js?v=5.1.3"></script>

  <script type="text/javascript" src="/js/src/schemes/pisces.js?v=5.1.3"></script>



  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.3"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.3"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.3"></script>



  


  




	





  





  
    <script type="text/javascript">
      (function(d, s) {
        var j, e = d.getElementsByTagName(s)[0];
        if (typeof LivereTower === 'function') { return; }
        j = d.createElement(s);
        j.src = 'https://cdn-city.livere.com/js/embed.dist.js';
        j.async = true;
        e.parentNode.insertBefore(j, e);
      })(document, 'script');
    </script>
  








  





  

  
<script>
(function(){
    var bp = document.createElement('script');
    var curProtocol = window.location.protocol.split(':')[0];
    if (curProtocol === 'https') {
        bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';        
    }
    else {
        bp.src = 'http://push.zhanzhang.baidu.com/push.js';
    }
    var s = document.getElementsByTagName("script")[0];
    s.parentNode.insertBefore(bp, s);
})();
</script><!-- hexo-inject:begin --><!-- hexo-inject:end -->


  

  
  


  

  

</body>
</html>
